agora inbox for [email protected]  
help / color / mirror / Atom feed
Support external parameters in EXECUTE command
964+ messages / 3 participants
[nested] [flat]

* Support external parameters in EXECUTE command
@ 2020-02-12 15:00  Peter Eisentraut <[email protected]>
  0 siblings, 1 reply; 964+ messages in thread

From: Peter Eisentraut @ 2020-02-12 15:00 UTC (permalink / raw)
  To: pgsql-hackers

Continuing the discussion in [0], here is a patch that allows parameter 
references in the arguments of the EXECUTE command.  The main purpose is 
submitting protocol-level parameters, but the added regression test case 
shows another way to exercise it.

What's confusing is that the code already contains a reference that 
indicates that this should be possible:

     /* Evaluate parameters, if any */
     if (entry->plansource->num_params > 0)
     {
         /*
          * Need an EState to evaluate parameters; must not delete it 
till end
          * of query, in case parameters are pass-by-reference.  Note 
that the
          * passed-in "params" could possibly be referenced in the parameter
          * expressions.
          */
         estate = CreateExecutorState();
         estate->es_param_list_info = params;
         paramLI = EvaluateParams(pstate, entry, stmt->params, estate);
     }

I'm not sure what this is supposed to do without my patch on top of it. 
If I remove the estate->es_param_list_info assignment, no tests fail 
(except the one I added).  Either this is a leftover from previous 
variants of this code (as discussed in [0]), or there is something I 
haven't understood.


[0]: 
https://www.postgresql.org/message-id/flat/6e7aa4a1-be6a-1a75-b1f9-83a678e5184a%402ndquadrant.com

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

From 9c1ba747f5c8b424ca8a1e4c0feb4c859312203e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 11 Feb 2020 19:55:25 +0100
Subject: [PATCH v1] Support external parameters in EXECUTE command

This allows the arguments of the EXECUTE command to be parameters
themselves, for example passed as separate parameters on the protocol
level.
---
 src/backend/commands/prepare.c        | 40 +++++++++++++++++++++++++++
 src/test/regress/expected/prepare.out | 23 +++++++++++++++
 src/test/regress/sql/prepare.sql      | 21 ++++++++++++++
 3 files changed, 84 insertions(+)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index c4e4b6eaec..543de3493d 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -33,6 +33,7 @@
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
 
@@ -175,6 +176,42 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
 						   true);
 }
 
+/*
+ * Parser callback for resolving parameter references from an existing
+ * ParamListInfo structure.
+ */
+static Node *
+pli_paramref_hook(ParseState *pstate, ParamRef *pref)
+{
+	ParamListInfo paramInfo = (ParamListInfo) pstate->p_ref_hook_state;
+	int			paramno = pref->number;
+	ParamExternData *ped;
+	ParamExternData pedws;
+	Param	   *param;
+
+	/* Check parameter number is valid */
+	if (paramno <= 0 || paramno > paramInfo->numParams)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("there is no parameter $%d", paramno),
+				 parser_errposition(pstate, pref->location)));
+
+	if (paramInfo->paramFetch != NULL)
+		ped = paramInfo->paramFetch(paramInfo, paramno, false, &pedws);
+	else
+		ped = &paramInfo->params[paramno - 1];
+
+	param = makeNode(Param);
+	param->paramkind = PARAM_EXTERN;
+	param->paramid = paramno;
+	param->paramtype = ped->ptype;
+	param->paramtypmod = -1;
+	param->paramcollid = get_typcollation(param->paramtype);
+	param->location = pref->location;
+
+	return (Node *) param;
+}
+
 /*
  * ExecuteQuery --- implement the 'EXECUTE' utility statement.
  *
@@ -199,6 +236,9 @@ ExecuteQuery(ParseState *pstate,
 	int			eflags;
 	long		count;
 
+	pstate->p_ref_hook_state = (void *) params;
+	pstate->p_paramref_hook = pli_paramref_hook;
+
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
index 3306c696b1..a0fec13c6b 100644
--- a/src/test/regress/expected/prepare.out
+++ b/src/test/regress/expected/prepare.out
@@ -187,3 +187,26 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements
 ------+-----------+-----------------
 (0 rows)
 
+-- check parameter handling
+CREATE TABLE t1 (a int);
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+SELECT f1(2);
+ f1 
+----
+   
+(1 row)
+
+SELECT * FROM t1;
+ a 
+---
+ 2
+(1 row)
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
index 985d0f05c9..6a16858482 100644
--- a/src/test/regress/sql/prepare.sql
+++ b/src/test/regress/sql/prepare.sql
@@ -78,3 +78,24 @@ CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA')
 DEALLOCATE ALL;
 SELECT name, statement, parameter_types FROM pg_prepared_statements
     ORDER BY name;
+
+
+-- check parameter handling
+
+CREATE TABLE t1 (a int);
+
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+
+SELECT f1(2);
+
+SELECT * FROM t1;
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;
-- 
2.25.0



Attachments:

  [text/plain] v1-0001-Support-external-parameters-in-EXECUTE-command.patch (3.7K, ../../[email protected]/2-v1-0001-Support-external-parameters-in-EXECUTE-command.patch)
  download | inline diff:
From 9c1ba747f5c8b424ca8a1e4c0feb4c859312203e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email protected]>
Date: Tue, 11 Feb 2020 19:55:25 +0100
Subject: [PATCH v1] Support external parameters in EXECUTE command

This allows the arguments of the EXECUTE command to be parameters
themselves, for example passed as separate parameters on the protocol
level.
---
 src/backend/commands/prepare.c        | 40 +++++++++++++++++++++++++++
 src/test/regress/expected/prepare.out | 23 +++++++++++++++
 src/test/regress/sql/prepare.sql      | 21 ++++++++++++++
 3 files changed, 84 insertions(+)

diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index c4e4b6eaec..543de3493d 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -33,6 +33,7 @@
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
 
@@ -175,6 +176,42 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
 						   true);
 }
 
+/*
+ * Parser callback for resolving parameter references from an existing
+ * ParamListInfo structure.
+ */
+static Node *
+pli_paramref_hook(ParseState *pstate, ParamRef *pref)
+{
+	ParamListInfo paramInfo = (ParamListInfo) pstate->p_ref_hook_state;
+	int			paramno = pref->number;
+	ParamExternData *ped;
+	ParamExternData pedws;
+	Param	   *param;
+
+	/* Check parameter number is valid */
+	if (paramno <= 0 || paramno > paramInfo->numParams)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("there is no parameter $%d", paramno),
+				 parser_errposition(pstate, pref->location)));
+
+	if (paramInfo->paramFetch != NULL)
+		ped = paramInfo->paramFetch(paramInfo, paramno, false, &pedws);
+	else
+		ped = &paramInfo->params[paramno - 1];
+
+	param = makeNode(Param);
+	param->paramkind = PARAM_EXTERN;
+	param->paramid = paramno;
+	param->paramtype = ped->ptype;
+	param->paramtypmod = -1;
+	param->paramcollid = get_typcollation(param->paramtype);
+	param->location = pref->location;
+
+	return (Node *) param;
+}
+
 /*
  * ExecuteQuery --- implement the 'EXECUTE' utility statement.
  *
@@ -199,6 +236,9 @@ ExecuteQuery(ParseState *pstate,
 	int			eflags;
 	long		count;
 
+	pstate->p_ref_hook_state = (void *) params;
+	pstate->p_paramref_hook = pli_paramref_hook;
+
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
index 3306c696b1..a0fec13c6b 100644
--- a/src/test/regress/expected/prepare.out
+++ b/src/test/regress/expected/prepare.out
@@ -187,3 +187,26 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements
 ------+-----------+-----------------
 (0 rows)
 
+-- check parameter handling
+CREATE TABLE t1 (a int);
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+SELECT f1(2);
+ f1 
+----
+   
+(1 row)
+
+SELECT * FROM t1;
+ a 
+---
+ 2
+(1 row)
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
index 985d0f05c9..6a16858482 100644
--- a/src/test/regress/sql/prepare.sql
+++ b/src/test/regress/sql/prepare.sql
@@ -78,3 +78,24 @@ CREATE TEMPORARY TABLE q5_prep_nodata AS EXECUTE q5(200, 'DTAAAA')
 DEALLOCATE ALL;
 SELECT name, statement, parameter_types FROM pg_prepared_statements
     ORDER BY name;
+
+
+-- check parameter handling
+
+CREATE TABLE t1 (a int);
+
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+
+SELECT f1(2);
+
+SELECT * FROM t1;
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;
-- 
2.25.0



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

* Re: Support external parameters in EXECUTE command
@ 2020-03-09 21:21  Tom Lane <[email protected]>
  parent: Peter Eisentraut <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Tom Lane @ 2020-03-09 21:21 UTC (permalink / raw)
  To: Peter Eisentraut <[email protected]>; +Cc: pgsql-hackers

Peter Eisentraut <[email protected]> writes:
> Continuing the discussion in [0], here is a patch that allows parameter 
> references in the arguments of the EXECUTE command.  The main purpose is 
> submitting protocol-level parameters, but the added regression test case 
> shows another way to exercise it.

I spent a bit of time looking at this.

> What's confusing is that the code already contains a reference that 
> indicates that this should be possible:
> ...
> I'm not sure what this is supposed to do without my patch on top of it. 
> If I remove the estate->es_param_list_info assignment, no tests fail 
> (except the one I added).  Either this is a leftover from previous 
> variants of this code (as discussed in [0]), or there is something I 
> haven't understood.

That case is reachable with an example like this:

    create or replace function foo(int) returns int language plpgsql as $$
    declare
      x int := $1;
      y int;
    begin
      execute 'execute fool($1 + 11)' into y using x;
      return y;
    end
    $$;

    deallocate fool;
    prepare fool(int) as select $1 + 1;

    select foo(42);

In the existing code this draws "ERROR:  there is no parameter $1".
Your patch makes it work, which is an improvement.

There are a few things bothering me, nonetheless.

1. The patch only implements part of the API for dynamic ParamListInfos,
that is it honors paramFetch but not parserSetup.  This seems not to
matter for any of the existing code paths (although we may just be lacking
a test case to reveal it); but I feel that it's just a matter of time
before somebody sets up a case where it would matter.  We would have such
a problem today if plpgsql treated EXECUTE as a plain SQL command rather
than its own magic thing, because then "EXECUTE prepared_stmt(plpgsql_var)"
would require the parserSetup hook to be honored in order to resolve the
variable reference.

It's fairly simple to fix this in ExplainExecuteQuery, since that is
creating its own ParseState; it can just apply the plist's parserSetup
to that pstate.  I did that in the attached v2 so you can see what
I'm talking about.  However, it's a lot less clear what to do in
ExecuteQuery, which as it stands is re-using a passed-in ParseState;
how do we know that the parse hooks aren't already set up in that?
(Or if they are, what do we do to merge their effects?)

2. Actually that latter problem exists already in your patch, because
it's cavalierly overwriting the passed-in ParseState's p_paramref_hook
without regard for the possibility that that's set already.  I added
an Assert that it's not set, and we get through check-world that way,
but even to write the assertion is to think that there is surely
going to be a code path that breaks it, soon if not already.

3. Both of the above points seem closely related to the vague worry
I had in the previous discussion about nested contexts all wanting
to control the resolution of parameters.  We'll get away with this,
perhaps, as long as that situation never occurs; but once it does
we have issues.

4. I'm inclined to feel that the reason we have these problems is
that this patch handles parameter resolution in the wrong place.
It would likely be better if parameter resolution were already
set up in the ParseState passed to ExecuteQuery (and then we'd fix
ExplainExecuteQuery by likewise passing it a ParseState for the
EXPLAIN EXECUTE).  However, that approach would probably result in
Params being available to any utility statement, and then we've
got issues for statements where expressions are only parsed and not
immediately executed: we have to define sane semantics for Param
references in such contexts, and make sure they get honored.

5. So that brings us back to the other point I made earlier, which
is that I'm not happy with patching this locally in EXECUTE rather
than having a design that works across-the-board for utility
statements.  You had expressed similar concerns a ways upthread:
>> Come to think of it, it would probably also be useful if PREPARE did
>> parameter processing, again in order to allow use with PQexecParams().

I think it's possible that we could solve the semantics problem
by defining the behavior for Params in utility statements as
"the parameter value is immediately substituted at parse time,
producing a Const node".  This doesn't change the behavior in EXECUTE,
because the expression will straightaway be evaluated and produce the
correct value.  In situations like

ALTER TABLE foo ADD COLUMN newcol int DEFAULT ($1);

you would also get what seem sane semantics, ie the default is the
value provided as a Param.

I feel that perhaps a patch that does this wouldn't be tremendously
more code than you have here; but the param resolution hook would be
installed at some different more-global place, and it would be code
to generate a Const node not a Param.

I attach a v2 with the trivial mods mentioned above, but just for
illustration not because I think this is the way to go.

			regards, tom lane



Attachments:

  [text/x-diff] v2-0001-Support-external-parameters-in-EXECUTE-command.patch (3.6K, ../../[email protected]/2-v2-0001-Support-external-parameters-in-EXECUTE-command.patch)
  download | inline diff:
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 284a5bf..af0755d 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -33,6 +33,7 @@
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "utils/snapmgr.h"
 #include "utils/timestamp.h"
 
@@ -176,6 +177,42 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
 }
 
 /*
+ * Parser callback for resolving parameter references from an existing
+ * ParamListInfo structure.
+ */
+static Node *
+pli_paramref_hook(ParseState *pstate, ParamRef *pref)
+{
+	ParamListInfo paramInfo = (ParamListInfo) pstate->p_ref_hook_state;
+	int			paramno = pref->number;
+	ParamExternData *ped;
+	ParamExternData pedws;
+	Param	   *param;
+
+	/* Check parameter number is valid */
+	if (paramno <= 0 || paramno > paramInfo->numParams)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_PARAMETER),
+				 errmsg("there is no parameter $%d", paramno),
+				 parser_errposition(pstate, pref->location)));
+
+	if (paramInfo->paramFetch != NULL)
+		ped = paramInfo->paramFetch(paramInfo, paramno, false, &pedws);
+	else
+		ped = &paramInfo->params[paramno - 1];
+
+	param = makeNode(Param);
+	param->paramkind = PARAM_EXTERN;
+	param->paramid = paramno;
+	param->paramtype = ped->ptype;
+	param->paramtypmod = -1;
+	param->paramcollid = get_typcollation(param->paramtype);
+	param->location = pref->location;
+
+	return (Node *) param;
+}
+
+/*
  * ExecuteQuery --- implement the 'EXECUTE' utility statement.
  *
  * This code also supports CREATE TABLE ... AS EXECUTE.  That case is
@@ -199,6 +236,10 @@ ExecuteQuery(ParseState *pstate,
 	int			eflags;
 	long		count;
 
+	Assert(pstate->p_paramref_hook == NULL);
+	pstate->p_ref_hook_state = (void *) params;
+	pstate->p_paramref_hook = pli_paramref_hook;
+
 	/* Look it up in the hash table */
 	entry = FetchPreparedStatement(stmt->name, true);
 
@@ -635,6 +676,9 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
 
 		pstate = make_parsestate(NULL);
 		pstate->p_sourcetext = queryString;
+		/* XXX what about p_queryEnv? */
+		if (params && params->parserSetup)
+			params->parserSetup(pstate, params->parserSetupArg);
 
 		/*
 		 * Need an EState to evaluate parameters; must not delete it till end
diff --git a/src/test/regress/expected/prepare.out b/src/test/regress/expected/prepare.out
index 3306c69..a0fec13 100644
--- a/src/test/regress/expected/prepare.out
+++ b/src/test/regress/expected/prepare.out
@@ -187,3 +187,26 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements
 ------+-----------+-----------------
 (0 rows)
 
+-- check parameter handling
+CREATE TABLE t1 (a int);
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+SELECT f1(2);
+ f1 
+----
+   
+(1 row)
+
+SELECT * FROM t1;
+ a 
+---
+ 2
+(1 row)
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;
diff --git a/src/test/regress/sql/prepare.sql b/src/test/regress/sql/prepare.sql
index 985d0f0..6a16858 100644
--- a/src/test/regress/sql/prepare.sql
+++ b/src/test/regress/sql/prepare.sql
@@ -78,3 +78,24 @@ SELECT name, statement, parameter_types FROM pg_prepared_statements
 DEALLOCATE ALL;
 SELECT name, statement, parameter_types FROM pg_prepared_statements
     ORDER BY name;
+
+
+-- check parameter handling
+
+CREATE TABLE t1 (a int);
+
+PREPARE p1 AS INSERT INTO t1 (a) VALUES ($1);
+
+CREATE FUNCTION f1(x int) RETURNS int
+LANGUAGE SQL
+AS $$
+EXECUTE p1($1);
+SELECT null::int;
+$$;
+
+SELECT f1(2);
+
+SELECT * FROM t1;
+
+DROP FUNCTION f1(int);
+DROP TABLE t1;


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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

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

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



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

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread

* [PATCH v47 4/9] repack code cleanups
@ 2026-03-31 14:48  Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 964+ messages in thread

From: Álvaro Herrera @ 2026-03-31 14:48 UTC (permalink / raw)

---
 src/backend/commands/cluster.c | 163 +++++++++++----------------------
 1 file changed, 51 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index fd0bfacaae6..c0220cb3380 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -2459,18 +2459,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	TupleTableSlot *spilled_tuple;
 	TupleTableSlot *old_update_tuple;
 	TupleTableSlot *ondisk_tuple;
-	MemoryContext apply_cxt;
 	bool		have_old_tuple = false;
-
-	/*
-	 * Use a separate memory context for the tuples and any memory needed to
-	 * process them.
-	 *
-	 * XXX would this be better with GenerationContextCreate?
-	 */
-	apply_cxt = AllocSetContextCreate(TopTransactionContext,
-									  "REPACK - apply",
-									  ALLOCSET_DEFAULT_SIZES);
+	MemoryContext	oldcxt;
 
 	spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 											 &TTSOpsVirtual);
@@ -2479,6 +2469,8 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 	old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
 												&TTSOpsVirtual);
 
+	oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
+
 	while (true)
 	{
 		size_t		nread;
@@ -2570,14 +2562,15 @@ apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
 		else
 			elog(ERROR, "unrecognized kind of change: %d", kind);
 
-		MemoryContextReset(apply_cxt);
+		ResetPerTupleExprContext(chgcxt->cc_estate);
 	}
 
 	/* Cleanup. */
 	ExecDropSingleTupleTableSlot(spilled_tuple);
 	ExecDropSingleTupleTableSlot(ondisk_tuple);
 	ExecDropSingleTupleTableSlot(old_update_tuple);
-	MemoryContextDelete(apply_cxt);
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
@@ -2588,27 +2581,17 @@ static void
 apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
 						ChangeContext *chgcxt)
 {
-	List	   *recheck;
-
 	/* Put the tuple in the table, but make sure it won't be decoded */
 	table_tuple_insert(rel, slot, GetCurrentCommandId(true),
 					   HEAP_INSERT_NO_LOGICAL, NULL);
 
-	/*
-	 * Update indexes with this new tuple.  Because we're merely replaying an
-	 * action that already happened, we have no use for the recheck list of
-	 * indexes returned, so just free it.  XXX or maybe just leave it?
-	 */
-	recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-									chgcxt->cc_estate,
-									0,
-									slot,
-									NIL, NULL);
-	list_free(recheck);
-
+	/* Update indexes with this new tuple. */
+	ExecInsertIndexTuples(chgcxt->cc_rri,
+						  chgcxt->cc_estate,
+						  0,
+						  slot,
+						  NIL, NULL);
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
-
-	ResetPerTupleExprContext(chgcxt->cc_estate);
 }
 
 /*
@@ -2624,10 +2607,9 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 	TM_FailureData tmfd;
 	TU_UpdateIndexes update_indexes;
 	TM_Result	res;
-	List	   *recheck;
 
 	/*
-	 * Carry out the update, avoiding logical decoding info.
+	 * Carry out the update, skipping logical decoding for it.
 	 */
 	res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
 							 GetCurrentCommandId(true),
@@ -2646,13 +2628,11 @@ apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
 
 		if (update_indexes == TU_Summarizing)
 			flags |= EIIT_ONLY_SUMMARIZING;
-		recheck = ExecInsertIndexTuples(chgcxt->cc_rri,
-										chgcxt->cc_estate,
-										flags,
-										spilled_tuple,
-										NIL, NULL);
-		list_free(recheck);
-		ResetPerTupleExprContext(chgcxt->cc_estate);
+		ExecInsertIndexTuples(chgcxt->cc_rri,
+							  chgcxt->cc_estate,
+							  flags,
+							  spilled_tuple,
+							  NIL, NULL);
 	}
 
 	pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
@@ -2665,10 +2645,7 @@ apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
 	TM_FailureData tmfd;
 
 	/*
-	 * Delete tuple from the new heap.
-	 *
-	 * Do it like in simple_heap_delete(), except for 'wal_logical' (and
-	 * except for 'wait').
+	 * Delete tuple from the new heap, skipping logical decoding for it.
 	 */
 	res = table_tuple_delete(rel, &(slot->tts_tid),
 							 GetCurrentCommandId(true),
@@ -3009,12 +2986,8 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	char		relpersistence;
 	bool		is_system_catalog;
 	Oid			ident_idx_new;
-	XLogRecPtr	wal_insert_ptr,
-				end_of_wal;
-	char		dummy_rec_data = '\0';
-	Relation   *ind_refs,
-			   *ind_refs_p;
-	int			nind;
+	XLogRecPtr	end_of_wal;
+	List	   *indexrels;
 	ChangeContext chgcxt;
 
 	/* Like in cluster_rel(). */
@@ -3041,24 +3014,23 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 */
 	ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
 
-	/* Find "identity index" on the new relation. */
+	/*
+	 * The identity index in the new relation appears in the same relative
+	 * position as the corresponding index in the old relation.  Find it.
+	 */
 	ident_idx_new = InvalidOid;
-	forboth(lc, ind_oids_old, lc2, ind_oids_new)
+	foreach_oid(ind_old, ind_oids_old)
 	{
-		Oid			ind_old = lfirst_oid(lc);
-		Oid			ind_new = lfirst_oid(lc2);
-
 		if (identIdx == ind_old)
 		{
-			ident_idx_new = ind_new;
+			ident_idx_new = list_nth_oid(ind_oids_new,
+										 foreach_current_index(ind_old));
 			break;
 		}
 	}
-
-	/* Should not happen, given our lock on the old relation. */
 	if (!OidIsValid(ident_idx_new))
-		ereport(ERROR,
-				errmsg("identity index missing on the new relation"));
+		elog(ERROR, "could not find index matching \"%s\" at the new relation",
+			 get_rel_name(identIdx));
 
 	/* Gather information to apply concurrent changes. */
 	initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
@@ -3074,8 +3046,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * incomplete page; see GetInsertRecPtr), to minimize the amount of data
 	 * we need to flush while holding exclusive lock on the source table.
 	 */
-	wal_insert_ptr = GetInsertRecPtr();
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3094,10 +3065,9 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	/*
 	 * Lock all indexes now, not only the clustering one: all indexes need to
 	 * have their files swapped. While doing that, store their relation
-	 * references in an array, to handle predicate locks below.
+	 * references in a zero-terminated array, to handle predicate locks below.
 	 */
-	ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old));
-	nind = 0;
+	indexrels = NIL;
 	foreach_oid(ind_oid, ind_oids_old)
 	{
 		Relation	index;
@@ -3105,16 +3075,11 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 		index = index_open(ind_oid, AccessExclusiveLock);
 
 		/*
-		 * TODO 1) Do we need to check if ALTER INDEX was executed since the
-		 * new index was created in build_new_indexes()? 2) Specifically for
-		 * the clustering index, should check_index_is_clusterable() be called
-		 * here? (Not sure about the latter: ShareUpdateExclusiveLock on the
-		 * table probably blocks all commands that affect the result of
-		 * check_index_is_clusterable().)
+		 * Some things about the index may have changed before we locked the
+		 * index, such as ALTER INDEX RENAME.  We don't need to do anything
+		 * here to absorb those changes in the new index.
 		 */
-		*ind_refs_p = index;
-		ind_refs_p++;
-		nind++;
+		indexrels = lappend(indexrels, index);
 	}
 
 	/*
@@ -3128,40 +3093,18 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	 * Tuples and pages of the old heap will be gone, but the heap will stay.
 	 */
 	TransferPredicateLocksToHeapRelation(OldHeap);
-	/* The same for indexes. */
-	for (int i = 0; i < nind; i++)
+	foreach_ptr(RelationData, index, indexrels)
 	{
-		Relation	index = ind_refs[i];
-
 		TransferPredicateLocksToHeapRelation(index);
-
-		/*
-		 * References to indexes on the old relation are not needed anymore,
-		 * however locks stay till the end of the transaction.
-		 */
 		index_close(index, NoLock);
 	}
-	pfree(ind_refs);
+	list_free(indexrels);
 
 	/*
-	 * Flush anything we see in WAL, to make sure that all changes committed
-	 * while we were waiting for the exclusive lock are available for
-	 * decoding. This should not be necessary if all backends had
-	 * synchronous_commit set, but we can't rely on this setting.
-	 *
-	 * Unfortunately, GetInsertRecPtr() may lag behind the actual insert
-	 * position, and GetLastImportantRecPtr() points at the start of the last
-	 * record rather than at the end. Thus the simplest way to determine the
-	 * insert position is to insert a dummy record and use its LSN.
-	 *
-	 * XXX Consider using GetLastImportantRecPtr() and adding the size of the
-	 * last record (plus the total size of all the page headers the record
-	 * spans)?
+	 * Flush WAL again, to make sure that all changes committed while we were
+	 * waiting for the exclusive lock are available for decoding.
 	 */
-	XLogBeginInsert();
-	XLogRegisterData(&dummy_rec_data, 1);
-	wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP);
-	XLogFlush(wal_insert_ptr);
+	XLogFlush(GetXLogInsertRecPtr());
 	end_of_wal = GetFlushRecPtr(NULL);
 
 	/*
@@ -3186,10 +3129,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 	{
 		Oid			ind_old = lfirst_oid(lc);
 		Oid			ind_new = lfirst_oid(lc2);
-		Oid			mapped_tables[4];
-
-		/* Zero out possible results from swapped_relation_files */
-		memset(mapped_tables, 0, sizeof(mapped_tables));
+		Oid			mapped_tables[4] = {0};
 
 		swap_relation_files(ind_old, ind_new,
 							(old_table_oid == RelationRelationId),
@@ -3200,13 +3140,12 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
 							mapped_tables);
 
 #ifdef USE_ASSERT_CHECKING
-
 		/*
 		 * Concurrent processing is not supported for system relations, so
 		 * there should be no mapped tables.
 		 */
 		for (int i = 0; i < 4; i++)
-			Assert(mapped_tables[i] == 0);
+			Assert(!OidIsValid(mapped_tables[i]));
 #endif
 	}
 
@@ -3256,22 +3195,22 @@ build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
 	pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
 								 PROGRESS_REPACK_PHASE_REBUILD_INDEX);
 
-	foreach_oid(ind_oid, OldIndexes)
+	foreach_oid(oldindex, OldIndexes)
 	{
-		Oid			ind_oid_new;
+		Oid			newindex;
 		char	   *newName;
 		Relation	ind;
 
-		ind = index_open(ind_oid, ShareUpdateExclusiveLock);
+		ind = index_open(oldindex, ShareUpdateExclusiveLock);
 
-		newName = ChooseRelationName(get_rel_name(ind_oid),
+		newName = ChooseRelationName(get_rel_name(oldindex),
 									 NULL,
 									 "repacknew",
 									 get_rel_namespace(ind->rd_index->indrelid),
 									 false);
-		ind_oid_new = index_create_copy(NewHeap, false, ind_oid,
-										ind->rd_rel->reltablespace, newName);
-		result = lappend_oid(result, ind_oid_new);
+		newindex = index_create_copy(NewHeap, false, oldindex,
+									 ind->rd_rel->reltablespace, newName);
+		result = lappend_oid(result, newindex);
 
 		index_close(ind, NoLock);
 	}
-- 
2.47.3


--2fxmamo6mu2qbxgv
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v47-0005-Use-BulkInsertState-when-copying-data-to-the-new.patch"



^ permalink  raw  reply  [nested|flat] 964+ messages in thread


end of thread, other threads:[~2026-03-31 14:48 UTC | newest]

Thread overview: 964+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-02-12 15:00 Support external parameters in EXECUTE command Peter Eisentraut <[email protected]>
2020-03-09 21:21 ` Tom Lane <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>
2026-03-31 14:48 [PATCH v47 4/9] repack code cleanups Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox